/* Project 7 Virtual Machine
   File: token.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "parser.h"

/* Prototypes needed in this file only */
char *Read_Token_Str(FILE *file);
void Remove_Str(char *str, int start, int end);
struct Token *Str_To_Token(char *token_str, FILE *error);
enum Opcode To_Opcode(char *str);
enum Register To_Register(char *str);
char* To_Label(char *str);
char* To_String(char *str);

/* Global variables needed in this file only */
char code_strs[OPCODECOUNT][6] = {"MOV", "ADD", "SUB", "MUL", "DIV", 
								  "GET", "FGET", "PUT", "JMNZ", "JMZ",
								  "JMP", "JMN", "GOTO"};

char register_strs[REGISTERCOUNT][6] = {"R0", "R1", "R2", "R3", 
										"R4", "R5", "R6", "R7"};
int line_num = 1;

/* Adds a token to the token list */
void Add_Token(struct TokenList *token_list, struct Token *token)
{
	if (token_list->head == NULL)
	{
		token_list->head = token;
		token_list->tail = token;
	}
	else
	{
		token_list->tail->next = token;
		token_list->tail = token;
	}		
}

/* Frees all the tokens in the token_list */
void Free_TokenList(struct TokenList *token_list)
{
	struct Token *next, *token = token_list->head;
	while (token != NULL)
	{
		next = token->next;
		free(token);
		token = next;
	}
	token_list->head = token_list->tail = NULL;
}

/* Returns 1 if it a line has more tokens
   and 0 if there are no more tokens */
int Has_More_Tokens(FILE *input)
{
	char c;

	/* Read in a char */
	while ((c = getc(input)) != EOF)
	{
		/* If it reached the end of a line, increase
		   the line number */
		if (c == '\n')
			++line_num;
		/* Otherwise, if the character isn't whitespace,
		   there are more tokens. Push the character
		   back on the stream to be read in later */
		else if (!isspace(c))
		{
			ungetc(c, input);
			return 1;
		}
	}

	/* If the end of the file was reached, there are no more tokens */
	return 0;
}

/* Returns 1 if the str is a float; 0 if not */
int Is_Float(char *str)
{
	int index_sign, index_point;

	/* Check for a negative sign in front */
	if (str[0] == '-')
		index_sign = 1;
	else
		index_sign = 0;

	/* Make sure there is a decimal point */
	if (strchr(str, '.') == NULL)
		return 0;

	/* Make sure there's only 1 decimal point */
	if (strchr(str, '.') != strrchr(str, '.'))
		return 0;

	/* Find it */
	index_point = strchr(str, '.') - str;

	/* Make sure all the characters between the sign
	   and the point are numbers */
	if (strspn(str + index_sign, "1234567890") != index_point - index_sign)
		return 0;

	/* Make sure all the character after the point
	   are numbers */
	return strspn(str + index_point + 1, "1234567890") == strlen(str + index_point + 1);
}

/* Returns 1 if the str is an integer; 0 if not */
int Is_Integer(char *str)
{
	int index;
	/* Check for a negative sign in front */
	if (str[0] == '-')
		index = 1;
	else
		index = 0;

	/* Make sure the other characters are numbers */
	return strspn(str + index, "1234567890") == (strlen(str) - index);
}

/* Returns 1 if the str is an opcode; 0 if not */
int Is_Opcode(char *str)
{
	char *upper_str = (char*)malloc(sizeof(char) * (strlen(str) + 1));
	unsigned int i;
	int r = 0;

	for (i = 0; i <= strlen(str); ++i)
		upper_str[i] = toupper(str[i]);

	for (i = 0; i < OPCODECOUNT; ++i)
		if (!strcmp(upper_str, code_strs[i]))
			r = 1;

	free(upper_str);
	return r;
}

/* Returns 1 if the str is an opcode; 0 if not */
int Is_Register(char *str)
{
	char *upper_str = (char*)malloc(sizeof(char) * (strlen(str) + 1));
	unsigned int i;
	int r = 0;

	for (i = 0; i <= strlen(str); ++i)
		upper_str[i] = toupper(str[i]);

	for (i = 0; i < REGISTERCOUNT; ++i)
		if (!strcmp(upper_str, register_strs[i]))
			r = 1;

	free(upper_str);
	return r;
}

/* Returns 1 if the str is a string literal; 0 if not */
int Is_Label(char *str)
{
	return str[strlen(str) - 1] == ':';
}

/* Returns 1 if the str is a string literal; 0 if not */
int Is_String(char *str)
{
	if (str[0] != '\"')
		return 0;
	return str[strlen(str) - 1] == '\"';
}

/* Returns 1 if the token is valid; 0 if not */
int Is_Valid_Token(struct Token *token)
{
	return token != NULL;
}

/* Creates a new TokenList */
struct TokenList New_TokenList()
{
	struct TokenList tl;
	tl.head = NULL;
	tl.tail = NULL;
	return tl;
}

void Print_Token(struct Token *token, FILE *output)
{
	if (token->type == INTEGER)
		fprintf(output, "Type: INTEGER  Value: %d\n", token->v.i);
	
	if (token->type == FLOAT)
		fprintf(output, "Type: FLOAT    Value: %g\n", token->v.f);
	
	if (token->type == OPCODE)
		fprintf(output, "Type: OPCODE   Value: %s\n", code_strs[token->v.op]);
	
	if (token->type == REGISTER)
		fprintf(output, "Type: REGISTER Value: %s\n", register_strs[token->v.r]);
	
	if (token->type == LABEL)
		fprintf(output, "Type: LABEL Value: %s\n", token->v.lbl);
	
	if (token->type == STRING)
		fprintf(output, "Type: STRING Value: %s\n", token->v.str);
}

/* Read in the next token from the file stream */
struct Token *Read_Token(FILE *input, FILE *error)
{
	return Str_To_Token(Read_Token_Str(input), error);
}

/* Reads in a line character by character into a customly
   sized buffer.  Very slow, but also very safe. =) */
char *Read_Token_Str(FILE *input)
{
	int length = 0, c, inside_string = 0, inside_escapecode = 0, escapecode_index = 0;
	char *token_str = NULL;

	/* Read until we reach the end of the line or the
	   end of the file */
	while ((c = getc(input)) != EOF)
	{
		if (c == '\"' && !inside_escapecode)
			inside_string = !inside_string;

		if (c == '\\' && !inside_escapecode)
		{
			inside_escapecode = 1;
			escapecode_index = length;
		}

		if (escapecode_index != length)
			inside_escapecode = 0;

		/* Increment length */
		++length;

		/* malloc or realloc based on length */
		if (length == 1)
			token_str = (char*)malloc(sizeof(char) * length);
		else
			token_str = (char*)realloc(token_str, sizeof(char) * length);

		/* Set the last character to c */
		token_str[length - 1] = (char)c;

		/* Check all whitespace */
		if (isspace(c) && !inside_string)
		{
			/* Let Has_More_Tokens handle the newline */
			if (c == '\n')
				ungetc(c, input);
			token_str[length - 1] = '\0';
			break;
		}
	}

	/* Return line */
	return token_str;
}

/* Remove characters from str starting at start
   through end.  The last character erased will
   be end - 1 */
void Remove_Str(char *str, int start, int end)
{
	memmove(str + start, str + end, strlen(str) - end + 1);
}

/* Removes the next token from line and
   return it in a Token struct */
struct Token *Str_To_Token(char *token_str, FILE *error)
{
	struct Token *token = (struct Token*)malloc(sizeof(struct Token));
	token->line_num = line_num;
	token->next = NULL;

	/* Check for an integer */
	if (Is_Integer(token_str))
	{
		token->type = INTEGER;
		token->v.i = atoi(token_str);
	} 
	/* Check for a float */
	else if (Is_Float(token_str))
	{
		token->type = FLOAT;
		token->v.f = (float)atof(token_str);
	}
	/* Check for an opcode */
	else if (Is_Opcode(token_str))
	{
		token->type = OPCODE;
		token->v.op = To_Opcode(token_str);
	}
	/* Check for a register */
	else if (Is_Register(token_str))
	{
		token->type = REGISTER;
		token->v.r = To_Register(token_str);
	}
	/* Check for a label */
	else if (Is_Label(token_str))
	{
		token->type = LABEL;
		token->v.lbl = To_Label(token_str);
	}
	/* Check for a label */
	else if (Is_String(token_str))
	{
		token->type = STRING;
		token->v.str = To_String(token_str);
	}
	/* Invalid type */
	else
	{
		/* Print an error message */
		fprintf(error, "Line %d: Unrecognized token '%s'\n", line_num, token_str);

		/* Clean up */
		free(token);
		token = NULL;
	}

	/* Clean up */
	free(token_str);

	/* Return the token */
	return token;
}

/* Returns 1 if the str is an opcode; 0 if not */
enum Opcode To_Opcode(char *str)
{
	char *upper_str = (char*)malloc(sizeof(char) * (strlen(str) + 1));
	
	unsigned int i;
	enum Opcode op = 0;

	for (i = 0; i <= strlen(str); ++i)
		upper_str[i] = toupper(str[i]);

	for (i = 0; i < OPCODECOUNT; ++i)
		if (!strcmp(upper_str, code_strs[i]))
			op = i;

	free(upper_str);
	return op;
}

/* Returns 1 if the str is an opcode; 0 if not */
enum Register To_Register(char *str)
{
	char *upper_str = (char*)malloc(sizeof(char) * (strlen(str) + 1));
	unsigned int i;
	enum Register r = 0;

	for (i = 0; i <= strlen(str); ++i)
		upper_str[i] = toupper(str[i]);

	for (i = 0; i < REGISTERCOUNT; ++i)
		if (!strcmp(upper_str, register_strs[i]))
			r = i;

	free(upper_str);
	return r;
}

/* Converts str to a label* */
char* To_Label(char *str)
{
	char *new_str = (char*)malloc(sizeof(char) * strlen(str));
	strncpy(new_str, str, strlen(str) - 1);
	new_str[strlen(str) - 1] = '\0';
	return new_str;
}

/* Converts str (String literal) to a char* */
char* To_String(char *str)
{
	char *new_str = (char*)malloc(sizeof(char) * (strlen(str) - 1));
	int i, j = 0;

	for (i = 1; i < strlen(str) - 1; ++i)
	{
		/* Check for an escape code */
		if (str[i] == '\\')
		{
			/* Check for newline */
			if (str[i + 1] == 'n')
			{
				new_str[j] = '\n';
				i++;
			}
			/* Check for quote " */
			else if (str[i + 1] == '\"')
			{
				new_str[j] = '\"';
				i++;
			}
			/* Check for backslash */
			else if (str[i + 1] == '\\')
			{
				new_str[j] = '\\';
				i++;
			}
			/* Just a random backslash */
			else
				new_str[j] = str[i];
		}
		/* Otherwise, just copy the character */
		else
			new_str[j] = str[i];
		j++;
	}

	new_str[j] = '\0';
	return new_str;
}

